/* SPF (Sender Policy Framework) implementation.

Translation to C Copyright (c) 2007 Roberto Alsina <ralsina@kde.org>
Copyright (c) 2003, Terence Way
Portions Copyright (c) 2004,2005,2006 Stuart Gathman <stuart@bmsi.com>
Portions Copyright (c) 2005,2006 Scott Kitterman <scott@kitterman.com>
This module is free software, and you may redistribute it and/or modify
it under the same terms as Python itself, so long as this copyright message
and disclaimer are retained in their original form.

IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

For more information about SPF, a tool against email forgery, see
http://www.openspf.org/

For new versions check out http://code.google.com/p/raspf

*/


#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <regex.h>

#include "bstrlib/bstrlib.h"
#include "udns/udns.h"
#include "hash/hash.h"
#include "excc/exception-local.h"

#include "dummy_dns.h"

#include "raspf.h"

struct bstrList *ALL_MECHANISMS;

spf_response spf_check (const char *ip, const char *sender,
                        const char *helo, const char *local,
                        const char *receiver)
{
    spf_query *q=spf_query_init(ip,
                                sender,
                                helo,
                                local,
                                receiver,
                                1);
    bstring spf=0;
    int mta_status_code=0;
    bstring explanation=bfromcstr("");
    bstring result=0;
    spf_response r;

    result=spf_query_check(q,spf,&mta_status_code,&explanation);
    r.code=mta_status_code;
    if (explanation)
    {
        r.explanation=bstr2cstr(explanation,0);
        bdestroy(explanation);
    }
    else
    {
        bstring s=bfromcstr("");
        r.explanation=bstr2cstr(s,0);
        bdestroy(s);
    }
    r.response=bstr2cstr(result,0);

    spf_query_destroy (q);
    bdestroy(result);
    return r;
}

/*A query object keeps the relevant information about a single SPF
 query:

 i: ip address of SMTP client in dotted notation
 s: sender declared in MAIL FROM:<>
 l: local part of sender s
 d: current domain, initially domain part of sender s
 h: EHLO/HELO domain
 v: 'in-addr' for IPv4 clients and 'ip6' for IPv6 clients
 p: SMTP client domain name
 o: domain part of sender s
 r: receiver
 c: pretty ip address (different from i for IPv6)

 This is also, by design, the same variables used in SPF macro
 expansion.

 */


/*
 Returns (result, mta-status-code, explanation) where result
 in ['fail', 'softfail', 'neutral' 'permerror', 'pass', 'temperror', 'none']

 Examples:
 >>> q = query(s='strong-bad@email.example.com',
 ...           h='mx.example.org', i='192.0.2.3')
 >>> q.check(spf='v=spf1 ?all')
 ('neutral', 250, 'access neither permitted nor denied')

 >>> q.check(spf='v=spf1 redirect=controlledmail.com exp=_exp.controlledmail.com')
 ('fail', 550, 'SPF fail - not authorized')

 >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ?all moo')
 ('permerror', 550, 'SPF Permanent Error: Unknown mechanism found: moo')

 >>> q.check(spf='v=spf1 =a ?all moo')
 ('permerror', 550, 'SPF Permanent Error: Unknown qualifier, RFC 4408 para 4.6.1, found in: =a')

 >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ~all')
 ('pass', 250, 'sender SPF authorized')

 >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 -all moo=')
 ('pass', 250, 'sender SPF authorized')

 >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 -all match.sub-domains_9=yes')
 ('pass', 250, 'sender SPF authorized')

 >>> q.strict = False
 >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 -all moo')
 ('permerror', 550, 'SPF Permanent Error: Unknown mechanism found: moo')
 >>> q.perm_error.ext
 ('pass', 250, 'sender SPF authorized')

 >>> q.strict = True
 >>> q.check(spf='v=spf1 ip4:192.1.0.0/16 moo -all')
 ('permerror', 550, 'SPF Permanent Error: Unknown mechanism found: moo')

 >>> q.check(spf='v=spf1 ip4:192.1.0.0/16 ~all')
 ('softfail', 250, 'domain owner discourages use of this host')

 >>> q.check(spf='v=spf1 -ip4:192.1.0.0/6 ~all')
 ('fail', 550, 'SPF fail - not authorized')

 # Assumes DNS available
 >>> q.check()
 ('none', 250, '')

 >>> q.check(spf='v=spf1 ip4:1.2.3.4 -a:example.net -all')
 ('fail', 550, 'SPF fail - not authorized')
 >>> q.libspf_local='ip4:192.0.2.3 a:example.org'
 >>> q.check(spf='v=spf1 ip4:1.2.3.4 -a:example.net -all')
 ('pass', 250, 'sender SPF authorized')

 >>> q.check(spf='v=spf1 ip4:1.2.3.4 -all exp=_exp.controlledmail.com')
 ('fail', 550, 'Controlledmail.com does not send mail from itself.')

 >>> q.check(spf='v=spf1 ip4:1.2.3.4 ?all exp=_exp.controlledmail.com')
 ('neutral', 250, 'access neither permitted nor denied')

 */

void destroy_mechanism(mechanism m)
{
    if (m.mech)
        bdestroy(m.mech);
    if (m.m)
        bdestroy(m.m);
    if (m.arg)
        bdestroy(m.arg);
    if (m.result)
        bdestroy(m.result);
    if (m.error.param1)
        bdestroy(m.error.param1);
}


bstring spf_query_check(struct spf_query *q, bstring _spf,
                        int *mta_status_code,
                        bstring *explanation)
{
    bassigncstr(*explanation,"");
    memset(q->mech,0,1000*sizeof(mechanism));
    q->mcount=0;
    q->perm_error.type=0;
    bstring spf=0;

    bstring rc=0;

    try
    {
        q->lookups=0;
        if (!_spf)
        {
            spf=spf_query_dns_spf(q,q->d);
        }

        if (q->libspf_local && _spf)
        {
            spf=insert_libspf_local_policy (_spf,
                                            q->libspf_local);
        }
        rc = spf_query_check1(q,spf, q->d, 0, mta_status_code,explanation);
        if (q->perm_error.type)
        {
            // lax processing encountered a permerror, but continued
            bstring msg=bstrcpy(q->perm_error.param1);
            bdestroy(q->perm_error.param1);
            throw (PERM_ERROR,msg);
        }
        bdestroy(spf);
    }
    except
    {
        on (TEMP_ERROR)
        {
            q->prob=bstrcpy(EXCEPTION.param1);
            bassignformat (*explanation,"SPF Temporary Error: %s",EXCEPTION.param1->data);
            bdestroy(EXCEPTION.param1);
            mta_status_code[0]=451;
            bdestroy(spf);
            rc=bfromcstr("temperror");
        }
        on (PERM_ERROR)
        {
            if (q->perm_error.type==0)
            {
                q->perm_error=EXCEPTION;
            }

            mta_status_code[0]=550;
            bassignformat(*explanation,
                          "SPF Permanent Error: %s",EXCEPTION.param1->data);
            bdestroy(EXCEPTION.param1);
            bdestroy(spf);
            rc=bfromcstr("permerror");
        }
    }
    return rc;
}

bstring spf_query_check1 (struct spf_query *q,
                          bstring spf,bstring domain,int recursion,
                          int *mta_status_code,
                          bstring *explanation)

{
    bassigncstr(*explanation,"");

    // spf rfc: 3.7 Processing Limits
    if (recursion > MAX_RECURSION )
    {
        /*
         This should never happen in strict mode
         because of the other limits we check,
         so if it does, there is something wrong with
         our code.  It is not a PermError because there is not
         necessarily anything wrong with the SPF record.
         */
        if (q->strict)
        {
            throw (ASSERTION_ERROR,bfromcstr("Too many levels of recursion"));
        }
        /* As an extended result, however, it should be
         a PermError.
         */
        throw (PERM_ERROR,bfromcstr("Too many levels of recursion"));
    }
    bstring tmp=q->d;
    try
    {
        //FIXME This is tricky! No idea if I got it right
        try
        {
            bassign(q->d,domain);
            bstring r=spf_query_check0(q,spf,recursion,mta_status_code,explanation);
            //q->d=bstrcpy(tmp);
            return r;
        }
        except
        {
            //q->d=bstrcpy(tmp);
            throw(EXCEPTION.type,EXCEPTION.param1);
        }
    }
    except
    {
        on (AMBIGUITY_WARNING)
        {
            q->prob=bstrcpy(EXCEPTION.param1);
            mta_status_code[0]=0;
            bassignformat(*explanation,"SPF Ambiguity Warning: %s",EXCEPTION.param1->data);
            bdestroy(EXCEPTION.param1);
            return bfromcstr("ambiguous");
        }
    }
}

void spf_query_set_explanation(spf_query *q,const char *exp)
{
    bstring r;

    struct tagbstring x1=bsStatic("softfail");
    r=hashtable_search(q->exps,&x1);
    bassigncstr(r,exp);

    struct tagbstring x2=bsStatic("fail");
    r=hashtable_search(q->exps,&x2);
    bassigncstr(r,exp);

    struct tagbstring x3=bsStatic("permerror");
    r=hashtable_search(q->exps,&x3);
    bassigncstr(r,exp);
}


mechanism spf_query_validate_mechanism(struct spf_query *q,bstring _mech)
{
    /* Parse and validate a mechanism.
     Returns a mechanism struct containing
     mech,m,arg,cidrlength,result

     Examples:
     >>> q = query(s='strong-bad@email.example.com.',
     ...           h='mx.example.org', i='192.0.2.3')
     >>> q.validate_mechanism('A')
     ('A', 'a', 'email.example.com', 32, 'pass')

     >>> q = query(s='strong-bad@email.example.com',
     ...           h='mx.example.org', i='192.0.2.3')
     >>> q.validate_mechanism('A')
     ('A', 'a', 'email.example.com', 32, 'pass')

     >>> q.validate_mechanism('?mx:%{d}/27')
     ('?mx:%{d}/27', 'mx', 'email.example.com', 27, 'neutral')

     >>> try: q.validate_mechanism('ip4:1.2.3.4/247')
     ... except PermError,x: print x
     Invalid IP4 CIDR length: ip4:1.2.3.4/247

     >>> try: q.validate_mechanism('ip4:1.2.3.4/33')
     ... except PermError,x: print x
     Invalid IP4 CIDR length: ip4:1.2.3.4/33

     >>> try: q.validate_mechanism('a:example.com:8080')
     ... except PermError,x: print x
     Invalid domain found (use FQDN): example.com:8080

     >>> try: q.validate_mechanism('ip4:1.2.3.444/24')
     ... except PermError,x: print x
     Invalid IP4 address: ip4:1.2.3.444/24

     >>> try: q.validate_mechanism('ip4:1.2.03.4/24')
     ... except PermError,x: print x
     Invalid IP4 address: ip4:1.2.03.4/24

     >>> try: q.validate_mechanism('-all:3030')
     ... except PermError,x: print x
     Invalid all mechanism format - only qualifier allowed with all: -all:3030

     >>> q.validate_mechanism('-mx:%%%_/.Clara.de/27')
     ('-mx:%%%_/.Clara.de/27', 'mx', '% /.Clara.de', 27, 'fail')

     >>> q.validate_mechanism('~exists:%{i}.%{s1}.100/86400.rate.%{d}')
     ('~exists:%{i}.%{s1}.100/86400.rate.%{d}', 'exists', '192.0.2.3.com.100/86400.rate.email.example.com', 32, 'softfail')

     >>> q.validate_mechanism('a:mail.example.com.')
     ('a:mail.example.com.', 'a', 'mail.example.com', 32, 'pass')

     >>> try: q.validate_mechanism('a:mail.example.com,')
     ... except PermError,x: print x
     Do not separate mechnisms with commas: a:mail.example.com,
     */

    // Things that need cleanup on exit
    mechanism mecha;
    mecha.result=0;
    mecha.m=0;
    mecha.arg=0;
    mecha.mech=0;
    mecha.error.param1=0;

    bstring result=bfromcstr("");
    bstring m=bfromcstr("");
    bstring arg=bfromcstr("");
    bstring mech=bstrcpy(_mech);
    bstring tmp=bfromcstr("");

    try
    {
        int cidrlength,cidr6length;
        if (mech->data[mech->slen-1] == ',')
        {
            // Do not separate mechnisms with commas
            mecha.error=spf_query_note_error(q,bformat("Do not separate mechanisms with commas: %s",mech->data));
            btrunc(mech,mech->slen-1);
        }

        // a mechanism
        parse_mechanism(mech,q->d,&m,&arg,&cidrlength,&cidr6length);
        // map '?' '+' or '-' to 'neutral' 'pass' or 'fail'
        if (m)
        {
            bassignmidstr(tmp,mech,0,1);
            bassign(result,hashtable_search(RESULTS,tmp));
            if (result->slen)
            {
                //eat '?' '+' or '-'
                bassignmidstr (tmp,m,1,m->slen-1);
                bassign(m,tmp);
            }
            else
            {
                // default pass
                bassigncstr(result,"pass");
            }
        }

        bstring tmp2=hashtable_search(COMMON_MISTAKES,m);
        if (tmp2)
        {
            spf_query_note_error(q,bformat("Unknown mechanism found: %s",mech->data));
            bassign(m,tmp2);
        }

        if (beqcstr(m,"a") && validate_ipv4(arg->data))
        {
            bstring msg=bformat("Use the ip4 mechanism for ip4 addresses: %s",
                                mech->data);
            mecha.error=spf_query_note_error(q,msg);
            bassigncstr(m,"ip4");
        }


        // validate cidr and dual-cidr
        if (beqcstr(m,"a") ||
            beqcstr(m,"mx"))
        {
            if (cidrlength==-1)
            {
                cidrlength=32;
            }
            else if (cidrlength > 32)
            {

                throw (PERM_ERROR,
                       bformat("Invalid IP4 CIDR length: %s",mech->data));
            }
            if (cidr6length==-1)
            {
                cidr6length=128;
            }
            else if (cidr6length > 128)
            {
                throw (PERM_ERROR,
                       bformat("Invalid IP6 CIDR length: %s",mech->data));
            }
            if (beqcstr(q->v,"ip6"))
            {
                cidrlength=cidr6length;
            }
        }
        else if (beqcstr(m,"ip4"))
        {
            if (cidr6length != -1)
            {
                throw(PERM_ERROR,
                      bfromcstr("Dual CIDR not allowed"));
            }
            if (cidrlength==-1)
            {
                cidrlength=32;
            }
            else if (cidrlength > 32)
            {
                throw(PERM_ERROR,
                      bformat("Invalid IP4 CIDR length: %s",mech->data));
            }
            if (!validate_ipv4(arg->data))
            {
                throw (PERM_ERROR,
                       bformat("Invalid IP4 address: %s",mech->data));
            }
        }
        else if (beqcstr(m,"ip6"))
        {
            if (cidr6length!=-1)
            {
                throw(PERM_ERROR,bformat("Dual CIDR not allowed: %s",mech->data));
            }
            if (cidrlength==-1)
            {
                cidrlength=128;
            }
            else if (cidrlength > 128)
            {
                throw (PERM_ERROR,
                       bformat("Invalid IP6 CIDR length: %s",mech->data));
            }
            struct in6_addr _ip6;
            int _r=dns_pton(AF_INET6,arg->data,&_ip6);
            if (0==_r)
            {
                throw (PERM_ERROR,bformat("Invalid IP6 address", mech->data));
            }
        }
        else
        {
            if (cidrlength != -1 || cidr6length !=-1 )
            {
                throw(PERM_ERROR,
                      bfromcstr("CIDR not allowed"));
            }
            cidrlength=q->cidrmax;
        }

        // validate domain-spec

        if ( beqcstr(m,"a") ||
            beqcstr(m,"mx") ||
            beqcstr(m,"ptr") ||
            beqcstr(m,"exists") ||
            beqcstr(m,"include"))
        {

            if (beqcstr(m,"exists") && (!arg || arg->slen==0) )
            {
                throw (PERM_ERROR,bformat("implicit exists not allowed: %s",mech->data));
            }

            bstring tt=spf_query_expand_domain(q,arg);
            bassign(arg,tt);
            bdestroy(tt);

            if (!arg || arg->slen==0)
            {
                throw (PERM_ERROR,bformat ("empty domain: %s",mech->data));
            }

            if (beqcstr(m,"include"))
            {
                if (1==biseq(arg,q->d))
                {
                    if (!beqcstr(mech,"include"))
                    {
                        throw (PERM_ERROR,
                               bformat("include has trivial recursion %s", mech));
                    }
                    throw(PERM_ERROR,
                          bformat("include mechanism missing domain %s", mech));
                }
            }
            mecha.mech=bstrcpy(mech);
            mecha.m=bstrcpy(m);
            mecha.arg=bstrcpy(arg);
            mecha.cidrlength=cidrlength;
            mecha.result=bstrcpy(result);
            throw(EXC_OK,0);
        }
        // validate 'all' mechanism per RFC 4408 ABNF
        if( beqcstr(m,"all") && index(mech->data,':'))
        {
            bstring msg=bformat("Invalid all mechanism format - only qualifier allowed with all: %s",
                                mech->data);
            mecha.error=spf_query_note_error(q,msg);
        }
        if (in_list (m,ALL_MECHANISMS))
        {
            mecha.mech=bstrcpy(mech);
            mecha.m=bstrcpy(m);
            mecha.arg=bstrcpy(arg);
            mecha.cidrlength=cidrlength;
            mecha.result=bstrcpy(result);
            throw(EXC_OK,0);
        }

        bassignmidstr(tmp,m,1,m->slen-1);
        if (in_list(tmp,ALL_MECHANISMS))
        {
            mecha.error=spf_query_note_error(q,bformat("Unknown qualifier, RFC 4408 para 4.6.1, found in %s", mech));
        }
        else
        {
            bstring msg=bformat("Unknown mechanism found: %s", mech->data);
            mecha.error=spf_query_note_error(q,msg);
            bdestroy(msg);
        }

        mecha.mech=bstrcpy(mech);
        mecha.m=bstrcpy(m);
        mecha.arg=bstrcpy(arg);
        mecha.cidrlength=cidrlength;
        mecha.result=bstrcpy(mecha.error.param1);
        throw(EXC_OK,0);
    }
    except
    {
        bdestroy(result);
        bdestroy(m);
        bdestroy(mech);
        bdestroy(arg);
        bdestroy(tmp);

        on (EXC_OK)
        {
            return mecha;
        }
        destroy_mechanism(mecha);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}


void parse_mechanism (bstring _mech,bstring d, bstring *name, bstring *domain,
                      int *cidr, int *cidr6)
{
    /* Breaks A, MX, IP4, and PTR mechanisms into a (name, domain,
     cidr,cidr6) tuple.  The domain portion defaults to d if not present,
     the cidr defaults to 32 if not present.

     Examples:
     >>> parse_mechanism('a', 'foo.com')
     ('a', 'foo.com', None, None)

     >>> parse_mechanism('a:bar.com', 'foo.com')
     ('a', 'bar.com', None, None)

     >>> parse_mechanism('a/24', 'foo.com')
     ('a', 'foo.com', 24, None)

     >>> parse_mechanism('A:foo:bar.com/16//48', 'foo.com')
     ('a', 'foo:bar.com', 16, 48)

     >>> parse_mechanism('-exists:%{i}.%{s1}.100/86400.rate.%{d}','foo.com')
     ('-exists', '%{i}.%{s1}.100/86400.rate.%{d}', None, None)

     >>> parse_mechanism('mx:%%%_/.Claranet.de/27','foo.com')
     ('mx', '%%%_/.Claranet.de', 27, None)

     >>> parse_mechanism('mx:%{d}//97','foo.com')
     ('mx', '%{d}', None, 97)

     >>> parse_mechanism('iP4:192.0.0.0/8','foo.com')
     ('ip4', '192.0.0.0', 8, None)
     */

    bdestroy(name[0]);
    bdestroy(domain[0]);

    // Things that need cleaning up
    bstring mech=bstrcpy(_mech);
    struct bstrList *a=re_split(mech->data,RE_DUAL_CIDR);

    if (a->qty==3)
    {
        // We keep the "//" of the cidr6, while python
        // loses it
        bassign(mech,a->entry[0]);
        btrunc(mech,mech->slen-2);


        cidr6[0]=atoi((a->entry[1]->data));
    }
    else
    {
        cidr6[0]=-1;
    }
    bstrListDestroy(a);
    a=re_split(mech->data,RE_CIDR);
    if (a->qty==3)
    {
        // We keep the "/" of the cidr, while python
        // loses it
        bassign(mech,a->entry[0]);
        btrunc(mech,mech->slen-1);

        cidr[0]=atoi((a->entry[1]->data));
    }
    else
    {
        cidr[0]=-1;
    }

    bstrListDestroy(a);
    a= bsplit(mech,':');
    if (a->qty <2)
    {
        name[0]=bstrcpy(mech);
        btolower(name[0]);
        if (beqcstr(mech,"exists"))
        {
            domain[0]=0;
        }
        else
        {
            domain[0]=bstrcpy(d);
        }
    }
    else
    {
        int i=0;
        domain[0]=bfromcstr("");
        name[0]=bstrcpy(a->entry[0]);
        for (i=1;i<a->qty;i++)
        {
            btolower(name[0]);
            bconcat(domain[0],a->entry[i]);
            bconchar(domain[0],':');
        }
        btrunc(domain[0],domain[0]->slen-1);
    }
    bstrListDestroy(a);
    bdestroy(mech);
}

void destroy_mechs(mechanism mechs[1000],int counter)
{
    int i;
    for (i=0;i<counter;i++)
        destroy_mechanism(mechs[i]);
}

bstring spf_query_check0 (struct spf_query *q,bstring _spf,int recursion,
                          int *mta_status_code,
                          bstring *explanation)

{
    /*Test this query information against SPF text.

    Returns (result, mta-status-code, explanation) where
    result in ['fail', 'unknown', 'pass', 'none']
    */


    struct tagbstring __def=bsStatic("neutral");
    bstring def=&__def;
    int mech_count=0;

    // Things that need cleaning up before leaving
    struct hashtable *modifiers=create_hashtable (16,hash_from_string,bstr_eq);
    struct bstrList *spf=0;
    struct bstrList *m=0;
    bassigncstr (*explanation,"");
    bstring result=bfromcstr("");
    bstring redirect=bfromcstr("");
    mechanism mechs[1000];
    bstring tmp=bfromcstr("");
    bstring redirect_record=0;
    bstring text=bfromcstr("");
    bstring mod=bfromcstr("");
    bstring arg=bfromcstr("");
    bstring res=bfromcstr("");
    // No more

    try
    {
        if (!_spf || _spf->slen==0)
        {
            *mta_status_code=250;
            bassigncstr(result,"none");
            bassign(*explanation,hashtable_search(EXPLANATIONS,result));
            throw (EXC_OK,0);
        }

        // split string by whitespace, drop the 'v=spf1'
        spf=bsplit(_spf,' ');

        /* Catch case where SPF record has no spaces.
         Can never happen with conforming dns_spf(), however
         in the future we might want to give warnings
         for common mistakes like IN TXT "v=spf1" "mx" "-all"
         in relaxed mode.
         */

        btolower(spf->entry[0]);
        if (!beqcstr(spf->entry[0],"v=spf1"))
        {
            /* I am not sure exactly what is supposed to happen here
             assert strict > 1
             raise AmbiguityWarning('Invalid SPF record in', self.d)
             */
        }

        // FIXME copy of explanations to be modified by exp=
        // exps = self.exps

        // no mechanisms at all cause unknown result, unless
        // overridden with 'default=' modifier

        // Look for modifiers
        int i;
        for (i=1;i<spf->qty;i++)
        {
            // Sometimes there are extra spaces and we get
            // useless entries in spf
            if (spf->entry[i]->slen==0)
            {
                continue;
            }
            if (m)
                bstrListDestroy(m);
            m=re_split(spf->entry[i]->data,RE_MODIFIER);
            if (m->qty !=3)
            {
                bstrListDestroy(m);
                m=0;
                mechs[mech_count]=spf_query_validate_mechanism(q,spf->entry[i]);
                mech_count++;
                continue;
            }

            // My RE_MODIFIER doesn't split **exactly** as it should
            // So we may have =explanation instead of explanation in
            // m->entry[2]
            if (m->entry[2]->data[0]=='=')
                bdelete(m->entry[2],0,1);

            bassign(mod,m->entry[1]);
            bassign(arg,m->entry[2]);

            if (hashtable_search(modifiers,mod))
            {
                if (beqcstr(mod,"redirect"))
                {
                    throw (PERM_ERROR,
                           bformat("redirect= MUST appear at most once: %s",
                                   mod->data));
                }
                spf_query_note_error(q,
                                     bformat("%s= MUST appear at most once: %s",
                                             m->entry[1]->data,mod->data));
                // Just use last one in lax mode
            }
            hashtable_insert(modifiers,bstrcpy(mod),
                             bfromcstr(""));

            if (beqcstr(mod,"exp"))
            {
                // Test exp-empty-domain
                // Ignore exp if empty domain-spec.

                if (arg && arg->slen>0)
                {
                    bstring tt=spf_query_expand_domain(q,arg);
                    if (tt)
                    {
                        bassign(arg,tt);
                        bdestroy(tt);
                    }
                    else
                    {
                        bdestroy(arg);
                        arg=0;
                    }
                    // always fetch explanation to check permerrors
                    bstring exp=spf_query_get_explanation(q,m->entry[2]);
                    if ( exp && exp->slen>0 && !recursion)
                    {
                        spf_query_set_explanation(q,exp->data);
                    }
                    if (exp) bdestroy(exp);
                }
            }
            else if (beqcstr(mod,"redirect"))
            {
                spf_query_check_lookups(q);
                bdestroy(redirect);
                redirect=spf_query_expand_domain(q,arg);
                if (!redirect)
                {
                    throw (PERM_ERROR, bformat("redirect has empty domain: %s",
                                               arg->data));
                }
            }
            else if (beqcstr(mod,"default"))
            {
                if (q->strict >1)
                {
                    throw (AMBIGUITY_WARNING, bformat("The default= modifier is obsolete."));
                }
                if (q->strict==0)
                {
                    // might be an old policy so do it anyway
                    bstring tt=spf_query_expand(q,arg,1);
                    bassign (arg,tt);
                    bdestroy (tt);
                    //default=- is the same as default=fail
                    bassign(def,hashtable_search (RESULTS,def));
                }
                // Else we ignore it.

            }
            else if (beqcstr(m->entry[1],"op"))
            {
                if (!recursion)
                {
                    struct bstrList *la=bsplit(arg,'.');
                    int i;
                    for (i=0;i<la->qty;i++)
                    {
                        bstring v=la->entry[i];
                        if (v->slen>0)
                        {
                            if (!hashtable_search (q->options,v))
                            {
                                //FIXME this should really be an int or something
                                hashtable_insert(q->options,bstrcpy(v),bfromcstr("TRUE"));
                            }
                        }
                        bdestroy(v);
                    }
                    bstrListDestroy(la);
                }
            }
            else
            {
                // spf rfc: 3.6 Unrecognized Mechanisms and Modifiers
                // syntax error on invalid macro
                bstring tt=spf_query_expand(q,m->entry[2],1);
                // It never actually gets here ;-)
                bdestroy(tt);
            }
        }

        int code;
        // Evaluate mechanisms
        //
        int flag=0;
        for (i=0;i<mech_count;i++)
        {
            flag=0;
            mechanism mech=mechs[i];
            bassign(result,mech.result);
            if (beqcstr(mech.m,"include"))
            {
                spf_query_check_lookups(q);
                bdestroy(tmp);
                tmp=0;
                tmp=spf_query_dns_spf(q,mech.arg);
                bdestroy(res);
                res=0;
                res=spf_query_check1(q,tmp,mech.arg,
                                        recursion +1,&code,&text);
                if (beqcstr(res,"pass"))
                {
                    flag=1;
                    break;
                }
                if (beqcstr(res,"none"))
                {
                    spf_query_note_error(q,
                                         bformat("No valid SPF record for included domain: %s %s",
                                                 mech.arg, mech));
                }
                // No idea why we bother putting neutral in res
                // if it loses scope right away, but hey, that's
                // how it's done in PySPF
                bassigncstr(res,"neutral");
                flag=1;
                continue;
            }
            else if (beqcstr(mech.m,"all"))
            {
                flag=1;
                break;
            }
            else if (beqcstr(mech.m,"exists"))
            {
                spf_query_check_lookups(q);
                struct bstrList *l=0;
                int ll=0;
                try
                {
                    struct tagbstring __a=bsStatic("A");
                    l=spf_query_dns_a(q,mech.arg,&__a);
                    if (l && l->qty>0)
                    {
                        ll=1;
                    }
                }
                except
                {
                    on (AMBIGUITY_WARNING)
                    {
                        // Exists wants no response sometimes so don't raise
                        // the warning.
                    }

                }
                bstrListDestroy(l);
                if (ll)
                {
                    flag=1;
                    break;
                }
            }
            else if (beqcstr(mech.m,"a"))
            {
                spf_query_check_lookups(q);
                struct bstrList *tl=spf_query_dns_a(q,mech.arg,q->A);
                if (tl)
                {
                    if (spf_query_cidrmatch(q,tl,mech.cidrlength))
                    {
                        bstrListDestroy(tl);
                        flag=1;
                        break;
                    }
                    bstrListDestroy(tl);
                }
            }
            else if (beqcstr(mech.m,"mx"))
            {
                spf_query_check_lookups(q);
                struct bstrList *l=spf_query_dns_mx(q,mech.arg);
                if (spf_query_cidrmatch(q,l,mech.cidrlength))
                {
                    bstrListDestroy(l);
                    flag=1;
                    break;
                }
                bstrListDestroy(l);
            }
            else if (beqcstr(mech.m,"ip4"))
            {
                //FIXME validate IPv4 syntax
                if (beqcstr(q->v,"in-addr"))
                    // match own connection type only
                {
                    if (! validate_ipv4(mech.arg->data))
                    {
                        throw(PERM_ERROR,
                              bformat("syntax error: %s",mech.mech->data));
                    }
                    if (spf_query_cidrmatch1(q,mech.arg,mech.cidrlength))
                    {
                        flag=1;
                        break;
                    }
                }
            }
            else if (beqcstr(mech.m,"ip6"))
            {
                //FIXME validate IPv6 syntax
                if (beqcstr(q->v,"ip6"))
                    // match own connection type only
                {
                    if (spf_query_cidrmatch1(q,mech.arg,mech.cidrlength))
                    {
                        flag=1;
                        break;
                    }
                }
            }
            else if (beqcstr(mech.m,"ptr"))
            {
                spf_query_check_lookups(q);
                struct bstrList *lt=spf_query_validated_ptrs(q);
                if (domainmatch(lt,mech.arg))
                {
                    bstrListDestroy(lt);
                    flag=1;
                    break;
                }
                bstrListDestroy(lt);
            }

        }
        if (0==flag)
        {
            // no matches
            if (redirect && redirect->slen)
            {
                // Catch redirect to a non-existant SPF record.
                redirect_record=spf_query_dns_spf(q,redirect);
                if (!redirect_record)
                {
                    throw (PERM_ERROR,
                           bformat("redirect domain has no SPF record %s",redirect->data));
                }
                // Forget modifiers on redirect
                if (!recursion)
                {
                    // FIXME what is this?
                    // self.exps = dict(self.defexps)
                    HASH_DESTROY(q->options);
                    q->options = create_hashtable (16,hash_from_string,bstr_eq);
                }
                bdestroy(result);
                result=0;
                result=spf_query_check1(q,redirect_record,redirect,recursion,
                                        mta_status_code,explanation);
                throw(EXC_OK,0);
            }
            else
            {
                bassign(result,def);
            }
        }
        if (beqcstr(result,"fail"))
        {
            mta_status_code[0]=550;
            bassign(*explanation,hashtable_search(q->exps,result));
            throw(EXC_OK,0);
        }
        else
        {
            mta_status_code[0]=250;
            bassign(*explanation,hashtable_search(q->exps,result));
            throw(EXC_OK,0);
        }
    }
    except
    {
        HASH_DESTROY(modifiers);
        if (spf) bstrListDestroy(spf);
        if (m) bstrListDestroy(m);
        if (redirect) bdestroy(redirect);
        if (tmp) bdestroy(tmp);
        if (text) bdestroy(text);
        if (arg) bdestroy(arg);
        if (mod) bdestroy(mod);
        if (redirect_record) bdestroy(redirect_record);
        if (res) bdestroy(res);
        destroy_mechs(mechs,mech_count);
        on (EXC_OK)
        {
            return result;
        }
        if (result) bdestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}

int spf_query_cidrmatch1 (spf_query *q,bstring _ip,int n)
{
    if (!_ip || !_ip->slen)
    {
        return 0;
    }
    if (n==0) //match with mask 0
    {
        return 1;
    }
    if (beqcstr(q->v,"ip6"))
    {
        struct in6_addr q_ip;
        struct in6_addr ip;
        dns_pton(AF_INET6,q->i->data,&q_ip);
        dns_pton(AF_INET6,_ip->data,&ip);

        unsigned char Q_IP[16];
        memcpy(Q_IP,q_ip.s6_addr,16);
        unsigned char IP[16];
        memcpy(IP,ip.s6_addr,16);
        int rem,div;
        rem=n%8;
        div=(n-rem)/8;

        if (div<16)
        {
            memset(Q_IP+div+1,0,16-div-1);
            memset(IP+div+1,0,16-div-1);
        }
        if (rem)
        {
            Q_IP[div]=Q_IP[div] & ((255<<(8-rem))&255);
            IP[div]=IP[div] & ((255<<(8-rem))& 255);
        }

        if (0==memcmp(IP,Q_IP,16))
        {
            return 1;
        }
        return 0;
    }
    else
    {
        unsigned long long MASK=0xFFFFFFFF;

        int b1,b2,b3,b4;
        int a1,a2,a3,a4;
        sscanf(q->i->data,"%d.%d.%d.%d",&a1,&a2,&a3,&a4);
        sscanf(_ip->data,"%d.%d.%d.%d",&b1,&b2,&b3,&b4);

        unsigned long long q_ip=a1*0x1000000+a2*0x10000+a3*0x100+a4;
        unsigned long long ip=b1*0x1000000+b2*0x10000+b3*0x100+b4;

        unsigned long long c=~(MASK>>n) & MASK & q_ip;
        unsigned long long d=~(MASK>>n) & MASK & ip;
        if (c == d)
        {
            return 1;
        }
    }
    return 0;
}

int spf_query_cidrmatch (spf_query *q,struct bstrList *list,int n)
{
    //Match connect IP against a list of other IP addresses.
    if (list)
    {
        int i;
        for (i=0;i<list->qty;i++)
        {
            if (spf_query_cidrmatch1(q,list->entry[i],n))
            {
                return 1;
            }
        }
    }
    return 0;
}

int domainmatch (struct bstrList *l,bstring domainsuffix)
{
    /*grep for a given domain suffix against a list of validated PTR
     domain names.

     Examples:
     >>> domainmatch(['FOO.COM'], 'foo.com')
     1

     >>> domainmatch(['moo.foo.com'], 'FOO.COM')
     1

     >>> domainmatch(['moo.bar.com'], 'foo.com')
     0
     */

    int result=0;
    bstring suff=bfromcstr("");
    bstring piece=bfromcstr("");
    if (l)
    {
        int i;
        for (i=0;i<l->qty;i++)
        {
            if (0==bstricmp(domainsuffix,l->entry[i]))
            {
                result=1;
            }
            else
            {

                bassignformat(suff,".%s",domainsuffix->data);
                bassignmidstr(piece,l->entry[i],
                              l->entry[i]->slen-suff->slen,
                              suff->slen);
                if (0==bstricmp(piece,suff))
                {
                    result=1;
                }
            }
        }
    }
    bdestroy (suff);
    bdestroy (piece);
    return result;
}

bstring spf_query_get_explanation(spf_query *q, bstring spec)
{
    bstring txt=0;
    struct bstrList *a=0;
    bstring result=0;

    try
    {
        // Expand an explanation
        if (spec && spec->slen)
        {
            a=spf_query_dns_txt(q,spec);

            if (a && a->qty==1)
            {
                try
                {
                    result=spf_query_expand(q,a->entry[0],0);
                    throw(EXC_OK,0);
                }
                except
                {
                    on (PERM_ERROR)
                    {
                        bdestroy(EXCEPTION.param1);
                        // RFC4408 6.2/4 syntax errors cause exp= to be ignored
                    }
                }
            }
        }
        if (q->strict >1)
        {
            throw (PERM_ERROR,bfromcstr("Empty domain-spec on exp="));
        }
        // RFC4408 6.2/4 empty domain spec is ignored
        // (unless you give precedence to the grammar).
        throw(EXC_OK,0);
    }
    except
    {
        if(txt) bdestroy(txt);
        if(a) bstrListDestroy(a);
        on (EXC_OK)
        {
            return result;
        }
        if(result) bdestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
    return 0;
}


void spf_query_destroy (spf_query *q)
{
    if (!q)
        return;
    bdestroy (q->i);
    bdestroy (q->s);
    bdestroy (q->l);
    bdestroy (q->d);
    bdestroy (q->h);
    bdestroy (q->v);
    bdestroy (q->p);
    bdestroy (q->o);
    bdestroy (q->r);
    bdestroy (q->c);
    bdestroy (q->A);
    bdestroy (q->ident);
    bdestroy (q->libspf_local);
    bdestroy (q->error);
    bdestroy (q->prob);

    HASH_DESTROY(q->exps);
    HASH_DESTROY(q->defexps);
    HASH_DESTROY(q->options);
#ifdef USE_DUMMY_DNS
    dummy_dns_free ();
#else
    dns_free (q->ctx);
#endif
    free (q);
}


struct spf_query *spf_query_init (const char *i,
                                  const char *s,
                                  const char *h,
                                  const char *local,
                                  const char *receiver,
                                  int strict)
{
    struct spf_query *q=malloc(sizeof(struct spf_query));

#ifdef USE_DUMMY_DNS
    dummy_dns_init(NULL);
#else
    q->ctx=dns_new(NULL);
    dns_init (q->ctx,1);
#endif

    q->prob=0;
    q->error=0;

    q->s=bfromcstr(s);
    q->h=bfromcstr(h);
    if ( h && !s)
    {
        bassignformat(q->s,"postmaster@%s",h);
        q->ident=bfromcstr("helo");
    }
    else
    {
        q->ident=bfromcstr("mailfrom");
    }
    q->l=0;
    q->o=0;
    split_email(s,h,&(q->l),&(q->o));
    q->d=bstrcpy(q->o);
    q->p=0; // Lazy evaluation
    if (receiver)
    {
        q->r=bfromcstr(receiver);
    }
    else
    {
        q->r=bfromcstr("unknown");
    }

    q->libspf_local=bfromcstr(local);

    q->exps = create_hashtable (16,hash_from_string,bstr_eq);
    HASH_INSERT (q->exps,"pass","sender SPF authorized");
    HASH_INSERT (q->exps,"fail","SPF fail - not authorized");
    HASH_INSERT (q->exps,"permerror","permanent error in processing");
    HASH_INSERT (q->exps,"temperror","temporary DNS error in processing");
    HASH_INSERT (q->exps,"softfail","domain owner discourages use of this host");
    HASH_INSERT (q->exps,"neutral","access neither permitted nor denied");
    HASH_INSERT (q->exps,"none","");
    HASH_INSERT (q->exps,"local","No SPF result due to local policy");
    HASH_INSERT (q->exps,"trusted","No SPF check - trusted-forwarder.org");
    HASH_INSERT (q->exps,"ambiguous","No error, but results may vary");

    q->defexps = create_hashtable (16,hash_from_string,bstr_eq);
    HASH_INSERT (q->defexps,"pass","sender SPF authorized");
    HASH_INSERT (q->defexps,"fail","SPF fail - not authorized");
    HASH_INSERT (q->defexps,"permerror","permanent error in processing");
    HASH_INSERT (q->defexps,"temperror","temporary DNS error in processing");
    HASH_INSERT (q->defexps,"softfail","domain owner discourages use of this host");
    HASH_INSERT (q->defexps,"neutral","access neither permitted nor denied");
    HASH_INSERT (q->defexps,"none","");
    HASH_INSERT (q->defexps,"local","No SPF result due to local policy");
    HASH_INSERT (q->defexps,"trusted","No SPF check - trusted-forwarder.org");
    HASH_INSERT (q->defexps,"ambiguous","No error, but results may vary");

    q->options = create_hashtable (16,hash_from_string,bstr_eq);

    q->lookups=0;
    q->strict=strict;

    spf_query_set_ip(q,i);

    return q;

}

void spf_query_set_ip(spf_query *q, const char *i)
{
    /* FIXME should verify that it is IPv6, too */

    q->c=0;
    q->i=0;
    q->A=0;
    q->v=0;
    q->cidrmax=0;

    if (!i)
    {
        // NO idea when this could happen, but trip something
        return;
    }

    bstring ii;

    int ip6=0;
    if (validate_ipv4(i))
    {
        ip6=0;
        ii=bfromcstr(i);
    }
    else
    {
        struct in6_addr ip;
        dns_pton(AF_INET6,i,&ip);
        unsigned char IP[16];
        memcpy(IP,ip.s6_addr,16);

        if (IP[0]+IP[1]+IP[2]+IP[3]+IP[4]+IP[5]+IP[6]+IP[7]+IP[8]+IP[9]==0
            && IP[10]==255 && IP[11]==255)
        {
            ii=bformat("%d.%d.%d.%d",
                       IP[12],
                       IP[13],
                       IP[14],
                       IP[15]);
            ip6=0;
        }
        else
        {
            ip6=1;
            ii=bfromcstr(i);
        }
    }
    if (ip6)
    {
        q->c=bstrcpy(ii);
        q->i=bstrcpy(ii);
        q->A=bfromcstr("AAAA");
        q->v=bfromcstr("ip6");
        q->cidrmax=128;
    }
    else
    {
        q->c=bstrcpy(ii);
        q->i=bstrcpy(ii);
        q->A=bfromcstr("A");
        q->v=bfromcstr("in-addr");
        q->cidrmax=32;
    }
    bdestroy(ii);
}

/* Figure out the validated PTR domain names for the connect IP.
 */


struct bstrList *spf_query_validated_ptrs( spf_query *q)
{
    // Basically: find the PTRs for the IP address in q->i,
    // Then lookup that name and see if it gives you
    // the same IP you started with.

    // Return a list of all so-validated names

    int max;
    //Things that need cleaning up before returning
    struct bstrList *ptrs=0;
    struct bstrList *ptrnames=0;
    struct bstrList *result=0;
    bstring tmp=bfromcstr("");

    try
    {

        // To prevent DoS attacks, more than 10 PTR names MUST NOT be looked up
        if (q->strict)
        {
            max=MAX_PTR;
            if (q->strict >1)
            {
                // Break out the number of PTR records returned for testing
                try
                {
                    ptrnames=spf_query_dns_ptr(q,q->i);
                    if (ptrnames->qty > max)
                    {
                        throw (AMBIGUITY_WARNING,
                               bformat("More than %d PTR records returned for %s",
                                       max,q->i->data));
                    }
                }
                except
                {
                }
            }
        }
        else
        {
            max=MAX_PTR*4;
        }

        int cidrlength=q->cidrmax;
        ptrs=spf_query_dns_ptr(q,q->i);
        if (ptrs)
        {
            if (max > ptrs->qty)
            {
                max=ptrs->qty;
            }

            int i;
            for (i=0;i<max;i++)
            {
                bstring p=ptrs->entry[i];
                struct bstrList *a=spf_query_dns_a(q,p,q->A);
                if (spf_query_cidrmatch(q,a,cidrlength))
                {
                    bconcat(tmp,p);
                    bconchar(tmp,0);
                }
                if (a)
                    bstrListDestroy(a);
            }
            result=bsplit(tmp,0);
            throw(EXC_OK,0);
        }
        throw(EXC_OK,0);
    }
    except
    {
        if (ptrs)
            bstrListDestroy(ptrs);
        if (ptrnames)
            bstrListDestroy(ptrnames);
        bdestroy(tmp);
        on (EXC_OK)
        {
            return (result);
        }
        if (result)
            bstrListDestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}

/* Compute p macro only if needed */
bstring spf_query_getp(spf_query *q)
{
    struct bstrList *l=0;
    if (!q->p)
    {
        l=spf_query_validated_ptrs(q);
        if (l)
        {
            int i;
            for (i=0;i<l->qty;i++)
            {
                if (0==bstricmp(l->entry[i],q->d))
                {
                    q->p=bstrcpy(q->d);
                }
            }
            if (!q->p)
            {
                q->p=bstrcpy(l->entry[0]);
            }
            bstrListDestroy(l);
        }
        else
        {
            q->p=bfromcstr("unknown");
        }
    }
    return q->p;
}



/*Given a sender email s and a HELO domain h, create a valid tuple
 (l, d) local-part and domain-part.

 Examples:
 >>> split_email('', 'wayforward.net')
 ('postmaster', 'wayforward.net')

 >>> split_email('foo.com', 'wayforward.net')
 ('postmaster', 'foo.com')

 >>> split_email('terry@wayforward.net', 'optsw.com')
 ('terry', 'wayforward.net')
 */

void
split_email (const char *_s,const char *h ,
             bstring *l, bstring *d)
{
    bdestroy(*l);
    bdestroy(*d);
    if (!_s || _s[0]==0)
    {
        l[0]=bfromcstr("postmaster");
        d[0]=bfromcstr(h);
        return;
    }
    else
    {
        bstring s=bfromcstr(_s);
        struct bstrList *parts = bsplit (s, '@');
        if (parts->entry[0]->slen==0)
        {
            bdestroy(parts->entry[0]);
            parts->entry[0]=bfromcstr("postmaster");
        }
        if (parts->qty == 2)
        {
            l[0]=bstrcpy(parts->entry[0]);
            d[0]=bstrcpy(parts->entry[1]);
        }
        else
        {
            l[0]=bfromcstr("postmaster");
            d[0]=bstrcpy(s);
        }

        bdestroy (s);
        bstrListDestroy (parts);
    }
    return;

}

struct bstrList *re_split(const char *string, const char *pattern)
{
    bstring tmp=re_split2(string,pattern,0);
    struct bstrList *l= bsplit(tmp,0);
    bdestroy(tmp);
    return l;

}

bstring re_split2(const char *string, const char *pattern,unsigned char joiner)
{
    int status;
    regex_t re;
    regmatch_t pmatch[20];

    int code=regcomp(&re, pattern, REG_ICASE|REG_EXTENDED);
    if (code!= 0)
    {
        char errbuf[1024];
        regerror (code,NULL,errbuf,1024);
        printf ("\nError compiling regex (%s: \n\t%s\n\n",pattern,errbuf);
        return(0);      /* Report error. */
    }

    bstring tmp=bfromcstr("");
    char *ptr=(char *)string;

    for (;;)
    {
        status = regexec(&re, ptr, (size_t)20, pmatch, 0);
        if (status==REG_NOMATCH)
        {
            break;
        }

        if (pmatch[1].rm_so==-1) // Match without groups
        {
            // Add piece until beginning of match
            bcatblk (tmp,ptr,pmatch[0].rm_so);
            bconchar (tmp,joiner);
            //Add match itself (this differs from python's split?)
            bcatblk (tmp,ptr+pmatch[0].rm_so,pmatch[0].rm_eo-pmatch[0].rm_so);
            bconchar (tmp,joiner);
            if (pmatch[0].rm_eo==0 && pmatch[0].rm_so==0)
            {
                ptr=ptr+1;
            }
            else
            {
                ptr=ptr+pmatch[0].rm_eo;
            }
        }
        else

        {
            /*w Match with groups (harder!)
             must split on each group instead of in the whole match.
             */
            char *x=ptr;
            int match;
            if (pmatch[1].rm_so==0) //Match at start
            {
                bcatblk (tmp,x,0);
                bconchar (tmp,joiner);
            }

            for (match=1;pmatch[match].rm_so!=-1;match++)
            {
                // The prematch piece from end of previous match
                int b=pmatch[match].rm_so-(x-ptr);
                if (b>0)
                {
                    bcatblk (tmp,x,b);
                    bconchar (tmp,joiner);
                }

                // The match piece
                bcatblk (tmp,ptr+pmatch[match].rm_so,pmatch[match].rm_eo-pmatch[match].rm_so);
                bconchar (tmp,joiner);

                //Move x to end of match
                x=ptr+pmatch[match].rm_eo;
            }
            ptr=x;
        }
        if (ptr[0]==0) //We consumed the whole string
        {
            break;
        }
    }
    regfree(&re);
    bcatblk (tmp,ptr,strlen(string)-(ptr-string));
    return tmp;

}

bstring re_find(const char *string, const char *pattern,
                int *begin, int *end)
{
    int status;
    regex_t re;
    regmatch_t pmatch[20];

    int code=regcomp(&re, pattern, REG_ICASE|REG_EXTENDED);
    if (code!= 0)
    {
        char errbuf[1024];
        regerror (code,NULL,errbuf,1024);
        printf ("\nError compiling regex (%s: \n\t%s\n\n",pattern,errbuf);
        return(0);      /* Report error. */
    }

    bstring tmp=bfromcstr("");
    status = regexec(&re, string, (size_t)20, pmatch, 0);
    if (status==REG_NOMATCH)
    {
        bdestroy(tmp);
        regfree(&re);
        return 0;
    }

    bcatblk (tmp,string+pmatch[0].rm_so,pmatch[0].rm_eo-pmatch[0].rm_so);
    begin[0]=pmatch[0].rm_so;
    end[0]=pmatch[0].rm_eo;
    regfree(&re);
    return tmp;
}



struct bstrList *re_findall(const char *string, const char *pattern)
{
    int status;
    regex_t re;
    regmatch_t pmatch[20];

    int code=regcomp(&re, pattern, REG_ICASE|REG_EXTENDED);
    if (code!= 0)
    {
        char errbuf[1024];
        regerror (code,NULL,errbuf,1024);
        return(0);      /* Report error. */
    }

    bstring tmp=bfromcstr("");
    char *ptr=(char *)string;

    for (;;)
    {
        status = regexec(&re, ptr, (size_t)20, pmatch, 0);
        if (status==REG_NOMATCH)
        {
            break;
        }
        bcatblk (tmp,ptr+pmatch[0].rm_so,pmatch[0].rm_eo-pmatch[0].rm_so);
        bconchar (tmp,0);
        ptr=ptr+pmatch[0].rm_eo;

    }
    regfree(&re);
    struct bstrList *l= bsplit(tmp,0);
    bdestroy(tmp);
    return l;

}

int
match(const char *string, char *pattern)
{
    int    status;
    regex_t    re;

    int code=regcomp(&re, pattern, REG_ICASE|REG_EXTENDED);
    if (code!= 0)
    {
        char errbuf[1024];
        regerror (code,NULL,errbuf,1024);
        printf ("\nError compiling regex (%s: \n\t%s\n\n",pattern,errbuf);
        return(0);      /* Report error. */
    }
    status = regexec(&re, string, (size_t) 0, NULL, 0);
    regfree(&re);
    if (status != 0) {
        return(0);      /* Report error. */
    }
    return(1);
}

void spf_query_check_lookups(spf_query *q)
{
    q->lookups=q->lookups+1;
    if (q->lookups > MAX_LOOKUP*4)
    {
        throw(PERM_ERROR,bformat("More than %d DNS lookups",MAX_LOOKUP*4));
    }
    if (q->lookups > MAX_LOOKUP)
    {
        spf_query_note_error(q,bformat("Too many DNS lookups"));
    }
}


exception_t spf_query_note_error(spf_query *q, bstring msg)
{
    if (q->strict)
    {
        throw (PERM_ERROR,msg);
    }
    // if lax mode, note error and continue
    if (q->perm_error.type == EXC_NONE)
    {
        q->perm_error=exception_make(PERM_ERROR,msg);
    }
    return q->perm_error;
}


bstring insert_libspf_local_policy (bstring spftxt,bstring _local)
{
    /*w Returns spftxt with local inserted just before last non-fail
     mechanism.  This is how the libspf{2} libraries handle "local-policy".

     Examples:
     >>> insert_libspf_local_policy('v=spf1 -all')
     'v=spf1 -all'
     >>> insert_libspf_local_policy('v=spf1 -all','mx')
     'v=spf1 -all'
     >>> insert_libspf_local_policy('v=spf1','a mx ptr')
     'v=spf1 a mx ptr'
     >>> insert_libspf_local_policy('v=spf1 mx -all','a ptr')
     'v=spf1 mx a ptr -all'
     >>> insert_libspf_local_policy('v=spf1 mx -include:foo.co +all','a ptr')
     'v=spf1 mx a ptr -include:foo.co +all'

     # FIXME: is this right?  If so, "last non-fail" is a bogus description.
     >>> insert_libspf_local_policy('v=spf1 mx ?include:foo.co +all','a ptr')
     'v=spf1 mx a ptr ?include:foo.co +all'
     >>> spf='v=spf1 ip4:1.2.3.4 -a:example.net -all'
     >>> local='ip4:192.0.2.3 a:example.org'
     >>> insert_libspf_local_policy(spf,local)
     'v=spf1 ip4:1.2.3.4 ip4:192.0.2.3 a:example.org -a:example.net -all'
     */

    if (!_local || _local->slen==0)
    {
        return bstrcpy(spftxt);
    }
    bstring local=bstrcpy(_local);
    struct bstrList *spf=bsplit(spftxt,' ');
    int i;
    int flag=0;
    bstring mech0=bfromcstr("");
    bstring result;
    /* map '?' '+' or '-' to 'neutral' 'pass' or 'fail' */
    for (i=spf->qty-1;i>0;i--)
    {
        /* actually finds last mech with default result */
        bdestroy(mech0);
        mech0=bmidstr(spf->entry[i],0,1);
        if ( NULL == hashtable_search (RESULTS,mech0))
        {
            bstring tmp=bfromcstr("");
            int j;
            for (j=1;j<=i;j++)
            {
                bconcat(tmp,spf->entry[j]);
                bconchar(tmp,' ');
            }
            bconcat(tmp,local);
            bconchar(tmp,' ');
            for (j=i+1;j<spf->qty;j++)
            {
                bconcat(tmp,spf->entry[j]);
                bconchar(tmp,' ');
            }
            flag=1;
            bdestroy(local);
            local=tmp;
            break;
        }
    }
    bdestroy (mech0);
    if (flag || spf->qty==1)
    {
        result=bformat("v=spf1 %s",local->data);
    }
    else
    {
        result=bstrcpy(spftxt);
    }
    //FIXME the way I make it adds extra whitespace sometimes.
    // No big deal really.
    btrimws(result);
    bstrListDestroy (spf);
    bdestroy (local);
    return result;
}

bstring spf_query_expand (spf_query *q,bstring str,int stripdot)
{
    /*Do SPF RFC macro expansion.

    Examples:
    >>> q = query(s='strong-bad@email.example.com',
    ...           h='mx.example.org', i='192.0.2.3')
    >>> q.p = 'mx.example.org'
    >>> q.r = 'example.net'

    >>> q.expand('%{d}')
    'email.example.com'

    >>> q.expand('%{d4}')
    'email.example.com'

    >>> q.expand('%{d3}')
    'email.example.com'

    >>> q.expand('%{d2}')
    'example.com'

    >>> q.expand('%{d1}')
    'com'

    >>> q.expand('%{p}')
    'mx.example.org'

    >>> q.expand('%{p2}')
    'example.org'

    >>> q.expand('%{dr}')
    'com.example.email'

    >>> q.expand('%{d2r}')
    'example.email'

    >>> q.expand('%{l}')
    'strong-bad'

    >>> q.expand('%{l-}')
    'strong.bad'

    >>> q.expand('%{lr}')
    'strong-bad'

    >>> q.expand('%{lr-}')
    'bad.strong'

    >>> q.expand('%{l1r-}')
    'strong'

    >>> q.expand('%{c}',stripdot=False)
    '192.0.2.3'

    >>> q.expand('%{r}',stripdot=False)
    'example.net'

    >>> q.expand('%{ir}.%{v}._spf.%{d2}')
    '3.2.0.192.in-addr._spf.example.com'

    >>> q.expand('%{lr-}.lp._spf.%{d2}')
    'bad.strong.lp._spf.example.com'

    >>> q.expand('%{lr-}.lp.%{ir}.%{v}._spf.%{d2}')
    'bad.strong.lp.3.2.0.192.in-addr._spf.example.com'

    >>> q.expand('%{ir}.%{v}.%{l1r-}.lp._spf.%{d2}')
    '3.2.0.192.in-addr.strong.lp._spf.example.com'

    >>> try: q.expand('%(ir).%{v}.%{l1r-}.lp._spf.%{d2}')
    ... except PermError,x: print x
    invalid-macro-char : %(ir)

    >>> q.expand('%{p2}.trusted-domains.example.net')
    'example.org.trusted-domains.example.net'

    >>> q.expand('%{p2}.trusted-domains.example.net.')
    'example.org.trusted-domains.example.net'

    >>> q = query(s='@email.example.com',
    ...           h='mx.example.org', i='192.0.2.3')
    >>> q.p = 'mx.example.org'
    >>> q.expand('%{l}')
    'postmaster'

    */

    const char *macro_delimiters = "{%-_";

    int end=0;
    bstring result=bfromcstr("");

    if (index(str->data,'%'))
    {
        struct bstrList *labels=bsplit(str,'.');
        int i;
        for (i=0;i<labels->qty;i++)
        {
            int is_macro=0;
            if (labels->entry[i]->slen > 1)
            {
                if (labels->entry[i]->data[0]=='%')
                {
                    int j=0;
                    for (j=0;j<4;j++)
                    {
                        if (labels->entry[i]->data[1] == macro_delimiters[j])
                        {
                            is_macro=1;
                        }
                    }
                    if (! is_macro)
                    {
                        bstring s=bformat("invalid-macro-char: %s",
                                          labels->entry[i]->data);
                        bstrListDestroy(labels);
                        bdestroy (result);
                        throw (PERM_ERROR,s);
                    }

                }
            }
        }
        bstrListDestroy(labels);
    }
    bstring macro=0;
    for (;;)
    {
        unsigned char letter;
        int _begin,_end;
        bdestroy(macro);
        macro=re_find(str->data+end,RE_CHAR,&_begin,&_end);
        if (macro==0)
        {
            break;
        }
        bcatblk(result,str->data+end,_begin);
        if (beqcstr(macro,"%%"))
        {
            bconchar(result,'%');
        }
        else if (beqcstr(macro,"%_"))
        {
            bconchar(result,' ');
        }
        else if (beqcstr(macro,"%-"))
        {
            bcatblk(result,"%20",3);
        }
        else
        {
            letter=tolower(macro->data[2]);
            if (letter=='p')
            {
                spf_query_getp(q);
            }
            else if (index("crt",letter) && stripdot)
            {
                bdestroy (result);
                bstring msg=bformat("c,r,t macros allowed in exp= text only",
                                    macro->data);
                bdestroy (macro);
                throw (PERM_ERROR,msg);
            }
            bstring expansion;
            switch (letter)
            {
            case 'i':
                expansion=bstrcpy(q->i);
                break;
            case 's':
                expansion=bstrcpy(q->s);
                break;
            case 'l':
                expansion=bstrcpy(q->l);
                break;
            case 'd':
                expansion=bstrcpy(q->d);
                break;
            case 'h':
                expansion=bstrcpy(q->h);
                break;
            case 'v':
                expansion=bstrcpy(q->v);
                break;
            case 'p':
                expansion=bstrcpy(q->p);
                break;
            case 'o':
                expansion=bstrcpy(q->o);
                break;
            case 'r':
                expansion=bstrcpy(q->r);
                break;
            case 'c':
                expansion=bstrcpy(q->c);
                break;
            case 'A':
                expansion=bstrcpy(q->A);
                break;
            default:
                {
                    bstring msg=bformat("Unknown Macro Encountered: ",macro->data);
                    bdestroy (result);
                    bdestroy (macro);
                    throw (PERM_ERROR,msg);
                }
            }
            if (expansion)
            {
                bstring joiner=bfromcstr("");
                bstring str=bfromcstr("");
                bconchar(joiner,letter);
                bstring mids=bmidstr(macro,3,macro->slen-4);
                bstring e=expand_one(expansion,mids,
                                     hashtable_search(JOINERS,joiner));
                if (letter != macro->data[2])
                {
                    bstring tmp=url_encode(e->data);
                    bassign(e,tmp);
                    bdestroy(tmp);
                }
                bconcat(result,e);
                bdestroy(e);
                bdestroy(mids);
                bdestroy(joiner);
                bdestroy(str);
                bdestroy(expansion);
            }
        }
        end+=_end;
    }
    bdestroy(macro);
    bcatblk(result,str->data+end,str->slen-end);
    if (stripdot && result->slen && result->data[result->slen-1]=='.')
    {
        btrunc(result,result->slen-1);
    }
    if (index(result->data,'.'))
    {
        if (result->slen > 253)
        {
            char *i=index(result->data,'.');
            bcatblk(result,i+1,strlen(i+1));
        }
    }
    return result;
}

struct bstrList *reverse_list(struct bstrList *l)
{
    int i;
    bstring tmp=bfromcstr("");
    for (i=l->qty;i>0;i--)
    {
        bconcat(tmp,l->entry[i-1]);
        bconchar(tmp,0);
    }
    bstrListDestroy(l);
    btrunc(tmp,tmp->slen-1);
    struct bstrList *l2=bsplit(tmp,0);
    bdestroy(tmp);
    return l2;
}

bstring expand_one(bstring expansion,bstring str,bstring joiner)
{
    if (!str || str->slen==0)
    {
        return bstrcpy(expansion);
    }
    bstring ln=0;
    bstring reverse=0;
    bstring delimiters=0;
    int flag=0;

    struct bstrList *l=re_split(str->data,RE_ARGS);
    if (l->qty >1)
    {
        ln=l->entry[1];
    }
    if (l->qty >2)
    {
        reverse=l->entry[2];
    }
    if (l->qty >3)
    {
        delimiters=l->entry[3];
    }

    if (!delimiters || delimiters->slen==0)
    {
        delimiters=bfromcstr(".");
        flag=1;
    }
    struct bstrList *el=split(expansion,delimiters,joiner);
    if (reverse && reverse->slen>0)
    {
        el=reverse_list(el);
    }
    int ind=0;
    if (ln && ln->slen >0)
    {
        ind=el->qty-atoi(ln->data)*2+1;
        if (ind<0)
        {
            ind=0;
        }
    }
    bstring tmp=bfromcstr("");
    for (;ind<el->qty;ind++)
    {
        bconcat (tmp,el->entry[ind]);
    }
    bstrListDestroy(l);
    bstrListDestroy(el);
    if (flag) bdestroy(delimiters);
    return tmp;
}

struct bstrList *split(bstring str,bstring delimiters,bstring joiner)
{
    /*Split a string into pieces by a set of delimiter characters.  The
     resulting list is delimited by joiner, or the original delimiter if
     joiner is not specified.

     Examples:
     >>> split('192.168.0.45', '.')
     ['192', '.', '168', '.', '0', '.', '45']

     >>> split('terry@wayforward.net', '@.')
     ['terry', '@', 'wayforward', '.', 'net']

     >>> split('terry@wayforward.net', '@.', '.')
     ['terry', '.', 'wayforward', '.', 'net']
     */

    int i,j;
    bstring result=bfromcstr("");
    bstring element=bfromcstr("");

    for (i=0;i<str->slen;i++)
    {
        unsigned char c=str->data[i];
        if (index(delimiters->data,c)) // c in delimiters
        {
            bconcat(result,element);
            bconchar(result,0);
            btrunc(element,0);
            if (joiner)
            {
                bconcat(result,joiner);
                bconchar(result,0);
            }
            else
            {
                bconchar(result,c);
                bconchar(result,0);
            }
        }
        else
        {
            bconchar(element,c);
        }
    }
    bconcat(result,element);
    struct bstrList *l=bsplit(result,0);
    bdestroy(result);
    bdestroy(element);
    return l;
}


/*w url_encode taken from http://mytears.org/resources/mysrc/c/urlquote/url.c
 I am guessing he took it from somewhere else too because this code
 is all over the place ;-)
 */

#define IS_ALNUM(ch) \
    ( ch >= 'a' && ch <= 'z' ) || \
    ( ch >= 'A' && ch <= 'Z' ) || \
    ( ch >= '0' && ch <= '9' ) || \
    ( ch >= '-' && ch <= '.' )

bstring url_encode( const char* str )
{

    int i, j = 0, len;

    char* tmp;

    len = strlen( str );
    tmp = (char*) malloc( (sizeof(char) * 3 * len) +1 );

    for( i = 0 ; i < len ; i++ ){

        if( IS_ALNUM( str[i] ) )
            tmp[j] = str[i];

        else{

            snprintf( &tmp[j], 4, "%%%02X\n", (unsigned char)str[i] );
            j += 2;

        }
        j++;

    }
    tmp[j] = 0;
    bstring r=bfromcstr(tmp);
    free(tmp);
    return r;
}

void spf_close (void)
{
    HASH_DESTROY(RESULTS);
    HASH_DESTROY(COMMON_MISTAKES);
    HASH_DESTROY(JOINERS);
    HASH_DESTROY(EXPLANATIONS);
    bstrListDestroy(ALL_MECHANISMS);
}


void spf_init (void)
{
#ifndef USE_DUMMY_DNS
    dns_init(NULL,1);
#endif
    RESULTS =  create_hashtable (16,hash_from_string,bstr_eq);
    HASH_INSERT (RESULTS,"+","pass");
    HASH_INSERT (RESULTS,"-","fail");
    HASH_INSERT (RESULTS,"?","neutral");
    HASH_INSERT (RESULTS,"~","softfail");
    HASH_INSERT (RESULTS,"pass","pass");
    HASH_INSERT (RESULTS,"fail","fail");
    HASH_INSERT (RESULTS,"permerror","permerror");
    HASH_INSERT (RESULTS,"error","temperror");
    HASH_INSERT (RESULTS,"neutral","neutral");
    HASH_INSERT (RESULTS,"softfail","softfail");
    HASH_INSERT (RESULTS,"none","none");
    HASH_INSERT (RESULTS,"local","local");
    HASH_INSERT (RESULTS,"trusted","trusted");
    HASH_INSERT (RESULTS,"ambiguous","ambiguous");
    HASH_INSERT (RESULTS,"unknown","permerror");

    COMMON_MISTAKES = create_hashtable (16,hash_from_string,bstr_eq);
    HASH_INSERT (COMMON_MISTAKES,"prt", "ptr");
    HASH_INSERT (COMMON_MISTAKES,"ip", "ip4");
    HASH_INSERT (COMMON_MISTAKES,"ipv4", "ip4");
    HASH_INSERT (COMMON_MISTAKES,"ipv6", "ip6");
    HASH_INSERT (COMMON_MISTAKES,"all.", "all");


    JOINERS = create_hashtable (16,hash_from_string,bstr_eq);
    HASH_INSERT (JOINERS,"l",".");
    HASH_INSERT (JOINERS,"s",".");


    EXPLANATIONS = create_hashtable (16,hash_from_string,bstr_eq);
    HASH_INSERT (EXPLANATIONS,"pass","sender SPF authorized");
    HASH_INSERT (EXPLANATIONS,"fail","SPF fail - not authorized");
    HASH_INSERT (EXPLANATIONS,"permerror","permanent error in processing");
    HASH_INSERT (EXPLANATIONS,"temperror","temporary DNS error in processing");
    HASH_INSERT (EXPLANATIONS,"softfail","domain owner discourages use of this host");
    HASH_INSERT (EXPLANATIONS,"neutral","access neither permitted nor denied");
    HASH_INSERT (EXPLANATIONS,"none","");
    HASH_INSERT (EXPLANATIONS,"local","No SPF results due to local policy");
    HASH_INSERT (EXPLANATIONS,"trusted","No SPF check - trusted-forwader.org");
    HASH_INSERT (EXPLANATIONS,"ambiguous","No errors, but results may vary");

    struct tagbstring s=bsStatic("a,mx,ptr,exists,include,ip4,ip6,all");
    ALL_MECHANISMS=bsplit(&s,',');
}


int in_list (bstring str,struct bstrList *list)
{
    if (!list)
        return 0;
    int i;
    for (i=0;i<list->qty;i++)
    {
        if (1==biseq(list->entry[i],str))
            return 1;
    }
    return 0;
}


bstring spf_query_get_header(spf_query *q,bstring res,bstring receiver)
{
    throw (UNIMPLEMENTED,bfromcstr("get_header not done yet"));
    /*
     if (!receiver || receiver->slen==0)
     {
     receiver=bstrcpy(q->r);
     }
     bstring client_ip=bstrcpy(q->c);
     bstring helo=quote_value(q->h);
     bstring envelope_from;
     bstring tag;

     if (1==biseq(q->ident,bfromcstr("helo")))
     {
     envelope_from=0;
     }
     else
     {
     envelope_from=quote_value(q->s);
     }
     if (1==biseq(res,bfromcstr("permerror") && q->mcount > 0))
     {
     tag=bjoin(," ");
     }
     */
}


int beqcstr(bstring b,const char *s)
{
    //Empty/NULL string comparison

    if (s==0 && b==0)
        return 1;
    bstring s1=bfromcstr(s);
    int r=biseq(b,s1);
    bdestroy (s1);
    if (r==-1)
    {
        throw (PERM_ERROR,
               bfromcstr("Error in beqcstr (biseq returned error)"));
    }
    else if (r==1)
        return 1;
    return 0;
}

#ifndef USE_DUMMY_DNS

struct bstrList *spf_query_dns_a(spf_query *q,bstring domainname, bstring A)
{

    char buffer[1024];

    // Things that need cleaning before returning
    bstring tmp=bfromcstr("");
    struct dns_rr_a4 *a4=0;
    struct dns_rr_a6 *a6=0;
    struct bstrList *result=0;

    try
    {
        int x;
        //Get a list of IP addresses for a domainname.
        if (domainname && domainname->slen)
        {
            if (!A || beqcstr(A,"A"))
            {
                a4=dns_resolve_a4(q->ctx,domainname->data,0);

                if (dns_status(q->ctx)!=DNS_E_NODATA && dns_status(q->ctx)!=DNS_E_NXDOMAIN )
                {
                    if (!a4)
                    {
                        throw (TEMP_ERROR,bformat("DNS_A %s",dns_error_text(q)));
                    }
                    int i;
                    for (i=0;i<a4->dnsa4_nrr;i++)
                    {
                        dns_ntop (AF_INET, &(a4->dnsa4_addr[i]), buffer, 1024);
                        bcatcstr (tmp, buffer);
                        bconchar(tmp,0);
                    }
                }
                while (tmp->slen>0 && tmp->data[tmp->slen-1]==0)
                {
                    btrunc (tmp,tmp->slen-1);
                }
                if (tmp->slen==0)
                {
                    throw (EXC_OK,0);
                }
                else
                {
                    result=bsplit(tmp,0);
                    throw (EXC_OK,0);
                }

            }
            else if (1==biseq(A,&_AAAA))
            {
                a6=dns_resolve_a6(q->ctx,domainname->data,0);
                if (dns_status(q->ctx)!=DNS_E_NODATA && dns_status(q->ctx)!=DNS_E_NODATA)
                {
                    if (!a6 || a6->dnsa6_nrr==0)
                    {
                        throw (TEMP_ERROR,bformat("DNS_AAAA %s",dns_error_text(q)));
                    }
                    int i;
                    for (i=0;i<a6->dnsa6_nrr;i++)
                    {
                        dns_ntop (AF_INET6, &(a6->dnsa6_addr[i]), buffer, 1024);
                        bcatcstr (tmp, buffer);
                        bconchar(tmp,0);
                    }
                }
                while (tmp->slen>0 && tmp->data[tmp->slen-1]==0)
                {
                    btrunc (tmp,tmp->slen-1);
                }
                if (tmp->slen==0)
                {
                    throw (EXC_OK,0);
                }
                else
                {
                    result=bsplit(tmp,0);
                    throw (EXC_OK,0);
                }

            }
            /* This should never be reachable, right?
            struct bstrList *l=bsplit(tmp,0);
            if (q->strict && l->qty ==0)
            {
                throw ( AMBIGUITY_WARNING,
                       bformat ("No %s records found for %s",
                                A,
                                domainname->data));
            }
            return l;
            */
        }
        throw (EXC_OK,0);
    }
    except
    {
        if (a4)
            free(a4);
        if (a6)
            free(a6);
        bdestroy(tmp);
        on (EXC_OK)
        {
            return result;
        }
        if (result)
            bstrListDestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}


struct bstrList *spf_query_dns_txt(spf_query *q,bstring domainname)
{

    // Things that need cleaning before returning
    struct bstrList *result=0;
    bstring tmp=bfromcstr("");
    struct dns_rr_txt *txt;

    try
    {
        if (domainname && domainname->slen)
        {
            txt=dns_resolve_txt(q->ctx,domainname->data,DNS_C_IN,0);
            if (dns_status(q->ctx)!=DNS_E_NODATA && dns_status(q->ctx)!=DNS_E_NXDOMAIN )
            {
                if (!txt)
                {
                    throw (TEMP_ERROR,bformat("DNS_TXT %s",dns_error_text(q)));
                }
                int i;
                for (i=0;i<txt->dnstxt_nrr;i++)
                {
                    bcatcstr(tmp,txt->dnstxt_txt[i].txt);
                    bconchar(tmp,0);
                }
            }
            while (tmp->slen>0 && tmp->data[tmp->slen-1]==0)
            {
                btrunc (tmp,tmp->slen-1);
            }
            if (tmp->slen==0)
            {
                throw(EXC_OK,0);
            }
            else
            {
                result=bsplit(tmp,0);
                throw(EXC_OK,0);
            }
        }
        throw(EXC_OK,0);
    }
    except
    {
        if (txt)
            free(txt);
        bdestroy(tmp);
        on (EXC_OK)
        {
            return result;
        }
        if (result)
            bstrListDestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}


struct bstrList *spf_query_dns_mx(spf_query *q,bstring domainname)
{
    /* Get a list of IP addresses for all MX exchanges for a
     domain name.
     */

    // Things that need cleaning before returning
    struct dns_rr_mx *mx;
    bstring tmp=bfromcstr("");
    struct bstrList *result=0;

    try
    {
        if (domainname && domainname->slen)
        {
            mx=dns_resolve_mx(q->ctx,domainname->data,0);

            if (dns_status(q->ctx)!=DNS_E_NODATA && dns_status(q->ctx)!=DNS_E_NXDOMAIN )
            {
                if (!mx)
                {
                    throw (TEMP_ERROR,bformat("DNS_MX %s",dns_error_text(q)));
                }
                int max;
                if (q->strict)
                {
                    max=MAX_MX;
                    if (q->strict>1)
                    {
                        if (mx->dnsmx_nrr> MAX_MX)
                        {
                            throw (AMBIGUITY_WARNING,
                                   bformat ("More than %d MX records returned %s",
                                            domainname->data));
                        }
                        if (mx->dnsmx_nrr == 0)
                        {
                            throw (AMBIGUITY_WARNING,
                                   bformat ("no MX records found for mx mechanism %s",
                                            domainname->data));
                        }
                    }
                }
                else
                {
                    max = MAX_MX*4;
                }
                if (max > mx->dnsmx_nrr)
                    max=mx->dnsmx_nrr;
                int i,j;
                for (i=0;i<max;i++)
                {
                    bstring _mx=bfromcstr(mx->dnsmx_mx[i].name);
                    struct bstrList *l2=spf_query_dns_a(q,_mx,q->A);
                    bdestroy(_mx);
                    if (!l2) //MX has no A record????
                    {
                        continue;
                    }

                    for (j=0;j<l2->qty;j++)
                    {
                        bconcat(tmp,l2->entry[j]);
                        bconchar(tmp,0);
                    }
                    bstrListDestroy(l2);
                }
            }
            while (tmp->slen>0 && tmp->data[tmp->slen-1]==0)
            {
                btrunc (tmp,tmp->slen-1);
            }
            if (tmp->slen==0)
            {
                throw(EXC_OK,0);
            }
            else
            {
                result=bsplit(tmp,0);
                throw(EXC_OK,0);
            }
        }
        throw(EXC_OK,0);
    }
    except
    {
        if (mx)
            free(mx);
        bdestroy(tmp);
        on (EXC_OK)
        {
            return result;
        }
        if (result)
            bstrListDestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}

struct bstrList *spf_query_dns_ptr(spf_query *q,bstring ip)
{
    // FIXME do IPv6?

    struct in_addr addr;

    // Things that need cleaning before returning
    struct dns_rr_ptr *ptr=0;
    struct bstrList *result=0;
    bstring tmp=bfromcstr("");
    try
    {
        int r=dns_pton(AF_INET,q->i->data,&addr);

        if (r <= 0)
        {
            throw(EXC_OK,0);
        }

        ptr=dns_resolve_a4ptr(q->ctx,&addr);

        if (!ptr ) //Query Failed
        {
            throw (TEMP_ERROR,bformat("DNS %s",dns_error_text(q)));
        }
        else
        {
            int i;
            for (i = 0; i < ptr->dnsptr_nrr; i++)
            {
                bcatcstr(tmp,ptr->dnsptr_ptr[i]);
                bconchar(tmp,0);
            }
            while (tmp->slen>0 && tmp->data[tmp->slen-1]==0)
            {
                btrunc (tmp,tmp->slen-1);
            }
            if (tmp->slen==0)
            {
                throw(EXC_OK,0);
            }
            else
            {
                result=bsplit(tmp,0);
                throw(EXC_OK,0);
            }
        }
        throw(EXC_OK,0);
    }
    except
    {
        if (ptr)
            free(ptr);
        bdestroy(tmp);
        on (EXC_OK)
        {
            return result;
        }
        if (result)
            bstrListDestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}


struct bstrList *spf_query_dns_99(spf_query *q,bstring domainname)
{
    // Things that need cleaning before returning
    struct bstrList *result=0;
    bstring tmp=bfromcstr("");
    struct dns_rr_spf *spf;

    try
    {

        if (domainname && domainname->slen)
        {
            spf=dns_resolve_spf(q->ctx,domainname->data,DNS_C_IN,0);
            if (dns_status(q->ctx)!= DNS_E_NODATA && dns_status(q->ctx)!= DNS_E_NXDOMAIN)
            {
                if (!spf || spf->dnsspf_nrr==0)
                {
                    throw (TEMP_ERROR,bformat("DNS_99 %s",dns_error_text(q)));
                }
                int i;
                for (i=0;i<spf->dnsspf_nrr;i++)
                {
                    bcatcstr(tmp,spf->dnsspf_spf[i].spf);
                    bconchar(tmp,0);
                }
            }
            while (tmp->slen>0 && tmp->data[tmp->slen-1]==0)
            {
                btrunc (tmp,tmp->slen-1);
            }
            if (tmp->slen==0)
            {
                throw(EXC_OK,0);
            }
            else
            {
                struct bstrList *l=bsplit(tmp,0);
                throw(EXC_OK,0);
            }
        }
        throw(EXC_OK,0);
    }
    except
    {
        free(spf);
        bdestroy(tmp);
        on (EXC_OK)
        {
            return result;
        }
        if (result)
            bstrListDestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
}


char *_E_TEMPFAIL="Temporary error";
char *_E_PROTOCOL="Protocol error";
char *_E_NXDOMAIN="The domain doesn't exist";
char *_E_NODATA="There is no data of the requested type";
char *_E_NOMEM="Out of memory";
char *_E_BADQUERY="Invalid query";

const char *dns_error_text(spf_query *q)
{
    switch(dns_status(q->ctx))
    {
    case  DNS_E_TEMPFAIL:
        return _E_TEMPFAIL;
    case  DNS_E_PROTOCOL:
        return _E_PROTOCOL;
    case  DNS_E_NXDOMAIN:
        return _E_NXDOMAIN;
    case  DNS_E_NODATA:
        return _E_NODATA;
    case  DNS_E_NOMEM:
        return _E_NOMEM;
    case  DNS_E_BADQUERY:
        return _E_BADQUERY;
    }
}

#else // USE_DUMMY_DNS

#include "dummy_dns.c"

#endif // USE_DUMMY_DNS

bstring spf_query_dns_spf(spf_query *q,bstring d)
/*w Get the SPF record recorded in DNS for a specific domain
 name.  Returns 0 if not found, or if more than one record
 is found.
 */

{
    /*w Per RFC 4.3/1, check for malformed domain.  This produces
     no results as a special case. */


    struct bstrList *labels=bsplit(d,'.');
    if (labels->qty==0)
    {
        bstrListDestroy(labels);
        return 0;
    }
    int i;
    for (i=0;i<labels->qty;i++)
    {
        if (labels->entry[i]->slen > 63)
        {
            bstrListDestroy(labels);
            return 0;
        }
    }
    bstrListDestroy(labels);


    // Things that have to be cleaned up before exit
    bstring result=bfromcstr("");
    bstring tmp=bfromcstr("");
    struct bstrList *a=0;
    struct bstrList *b=0;
    struct bstrList *l=0;

    try
    {

        try
        {
            l=spf_query_dns_99(q,d);
            bassigncstr(tmp,"");
            if (l)
            {
                for (i=0;i<l->qty;i++)
                {
                    if (match( l->entry[i]->data,RE_SPF))
                    {
                        bconcat(tmp,l->entry[i]);
                        bconchar(tmp,0);
                    }
                }
                bstrListDestroy(l);
            }
            if (tmp && tmp->slen>0)
            {
                if (tmp->data[tmp->slen]==0)
                    btrunc(tmp,tmp->slen-1);
                b=bsplit(tmp,0);
            }
        }
        except
        {
            on (TEMP_ERROR)
            {
                // some braindead DNS servers hang on type 99 query
                if (q->strict > 1)
                {
                    throw (PERM_ERROR,EXCEPTION.param1);
                }
                bdestroy(EXCEPTION.param1);
                b=0;
            }
        }

        if (b && b->qty >1)
        {
            throw (PERM_ERROR,bfromcstr("Two or more type SPF spf records found."));
        }

        // Changed according to test spfoverride
        if (b && b->qty==1)
        {
            bassign(result,b->entry[0]);
            throw(EXC_OK,0);
        }

        l=spf_query_dns_txt(q,d);
        bassigncstr(tmp,"");
        if (l)
        {
            for (i=0;i<l->qty;i++)
            {
                if (match( l->entry[i]->data,RE_SPF))
                {
                    bconcat(tmp,l->entry[i]);
                    bconchar(tmp,0);
                }
            }
            bstrListDestroy(l);
        }
        if (tmp && tmp->slen>0)
        {
            if (tmp->data[tmp->slen]==0)
                btrunc(tmp,tmp->slen-1);
            a=bsplit(tmp,0);
            btrunc(tmp,0);
        }

        // Moved here to pass test multispf2:
        // Older implementations will give pass because there is a single TXT
        // record.  But RFC 4408 requires permerror because the SPF records
        // override and there are more than one.

        if (a && a->qty==1 && q->strict <2)
        {
            bassign(result,a->entry[0]);
            throw(EXC_OK,0);
        }


        // Moved to pass SPF test multitxt1 (SPF record has priority over
        // multiple TXT records as per RFC 4408)
        if (a && a->qty >1)
        {
            throw (PERM_ERROR,
                   bfromcstr("Two or more type TXT spf records found."));
        }
        if (a && a->qty==1)
        {
            bassign(result,a->entry[0]);
            throw(EXC_OK,0);
        }
        // FIXME do DELEGATE (?)
        bdestroy(result);
        result=0;
        throw(EXC_OK,0);
    }
    except
    {
        // Clean up everything except result.
        if (a) bstrListDestroy(a);
        if (b) bstrListDestroy(b);
        bdestroy(tmp);
        on (EXC_OK)
        {
            return result;
        }
        // All other exceptions, cleanup result and rethrow
        bdestroy(result);
        throw(EXCEPTION.type,EXCEPTION.param1);
    }
    // Should never reach here
    return 0;
}

bstring spf_query_best_guess (spf_query *q, bstring _spf,
                              int *mta_status_code,
                              bstring *explanation)
{

    if (0==validate_domain_spec (q->d->data))
    {
        *mta_status_code=250;
        bassigncstr(*explanation,"");
        return bfromcstr("none");
    }

    bstring spf=bfromcstr(DEFAULT_SPF);
    if (_spf)
    {
        bassign(spf,_spf);
    }
    bstring result=spf_query_check(q,spf,mta_status_code,explanation);
    bdestroy (spf);
    return result;
}


bstring spf_query_expand_domain( spf_query *q, bstring arg )
{
    char *s=bstr2cstr(arg,0);
    int res=validate_domain_spec(s);
    free(s);
    if (res==0)
    {
        bstring msg=bformat("Invalid domain found (use FQDN): %s", arg->data);
        throw (PERM_ERROR,msg);
    }
    else
    {
        return spf_query_expand(q,arg,1);
    }
}
